﻿<!DOCTYPE html>
<html>

  <head>
    <title>canvas</title>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <style>
      body {
        padding: 0px!important;
        margin: 0;
        background:#fff;
      }
      
      #a {
        position: relative;
        height: 400px;
        margin: auto;
      }
      
      #canvas {
        position: absolute;
        top: 0;
        left: 0
      }
    </style>
  </head>

  <body>
    <div id='a' style=''>
      <canvas id='canvas' style=''></canvas>
    </div>

  </body>
  <script>
    // 闭包
    (function() {
      // 公共数据
      let canvas;
      let ctx;
      let width;
      let height;
      let swingR = 70; // 气泡摆动半径
      let bubbleImportWidth = 100; // 气泡出口范围
      let bubbleR = 13; // 气泡半径
      let initBubbleOpacity = 0.8; //气泡初始透明度
      let colors = ['#FF0000', '#FFFF00', '#FF7F00', '#00FF00', '#00FFFF', '#0000FF', '#8B00FF'];
      let status = 'rise'; // 气泡状态，rise上升，bomb爆炸，bombOver爆炸结束
      let bombTime = 1000; // 爆炸时间
      let bubbles = [];

      // 定义气泡
      class Bubble {
        constructor(obj) {
          this.bubbleR = obj.bubbleR; // 半径
          this.axisX = obj.axisX; // 水平位置
          this.axisY = obj.axisY; // 垂直位置
          this.bubbleOpacity = obj.bubbleOpacity; // 透明度
          this.bubbleColor = obj.bubbleColor;
          this.swingCenterLine = obj.swingCenterLine; // 摆动中线
          this.initSwingAngle = obj.initSwingAngle; // 初始摆动角度
          this.swingR = obj.swingR; // 摆动半径

          this.riseSpeed = obj.riseSpeed; // 上升速度
          this.createdTime = new Date().getTime(); // 创建时间
          this.status = obj.status; // 状态，rise上升，bomb爆炸，bombOver爆炸结束
          this.bombBubbles = [];
          for(let i = 0; i < 8; i++) {
            this.bombBubbles.push({
              initOpacity: 1,
              opacity: 1,
              bombTime: 0,
              initAxisX: 0,
              initAxisY: 0,
              length: 0,
              radius: 2,
              speed: 80,
              angle: i * 360 / 8,
              color: this.bubbleColor
            })
          }

          return this
        }
      }
      
      // 设置画布
      (function() {
        let a = document.getElementById('a');
        height = parseFloat(window.getComputedStyle(a, null).getPropertyValue('height'));
        width = parseFloat(window.getComputedStyle(a, null).getPropertyValue('width'));
        document.getElementById("canvas").width = width;
        document.getElementById("canvas").height = height;
      })();

      // 创建气泡
      function createBubble() {
        let obj = {};
        obj.initSwingAngle = parseInt(Math.random() * 360);
        obj.swingCenterLine = parseInt(Math.random() * bubbleImportWidth) + parseInt((width - bubbleImportWidth) / 2);
        obj.riseSpeed = parseInt(Math.random() * 60 + 60);
        obj.bubbleColor = colors[parseInt(Math.random() * 7)];
        obj.axisX = 0;
        obj.axisY = height;
        obj.bubbleOpacity = initBubbleOpacity;
        obj.swingR = swingR;
        obj.bubbleR = parseInt(Math.random() * 5 + 12);
        obj.status = status;
        bubbles.push(new Bubble(obj))

      }

      // 计算气泡位置,透明度,溢出移除

      function cpBubblePosition() {

        let arr = [];
        bubbles.forEach((item, index) => {
          if((item.axisY < 20 && item.status === 'rise') || item.status === 'bombOver') {
            arr.push(item)
          }
        })

        arr.forEach((item, index) => {
          bubbles.splice(bubbles.indexOf(item), 1)
        })

        // 遍历气泡，计算气泡的实时水平和垂直方向位置，
        bubbles.forEach((item, index) => {
          if(item.status === 'rise') {
            // 计算气泡位置等
            let time = (new Date().getTime() - item.createdTime) / 1000;
            item.axisY = height - time * item.riseSpeed;

            let angle = parseInt((time * item.riseSpeed % (item.swingR * 4)) / (item.swingR * 4) * 360) - item.initSwingAngle;

            item.axisX = item.swingCenterLine - Math.sin(angle * (Math.PI / 180)) * item.swingR * 0.3;
            item.bubbleOpacity = 1 - time * item.riseSpeed / height;
            item.bubbleOpacity = item.bubbleOpacity < initBubbleOpacity ? item.bubbleOpacity : initBubbleOpacity;
          } else {
            // 计算爆炸气泡
            item.bombBubbles.forEach((bombBubble) => {
              bombBubble.length = (new Date().getTime() - bombBubble.bombTime) / 1000 * bombBubble.speed * 0.5;
              bombBubble.opacity = bombBubble.initOpacity * (1 - (new Date().getTime() - bombBubble.bombTime) / bombTime);
              bombBubble.opacity = bombBubble.opacity < 0 ? 0 : bombBubble.opacity;
            })
          }

        })
      }

      // 点击气泡爆炸
      function bomb(event) {
        event = event || window.event;
        let axisX = event.offsetX;
        let axisY = event.offsetY;

        bubbles.forEach((bubble) => {
          if(bubble.status !== 'rise') return;
          if(axisX <= bubble.axisX + 2 * bubble.bubbleR && axisX >= bubble.axisX - 2 * bubble.bubbleR && axisY <= bubble.axisY + 2 * bubble.bubbleR && axisY >= bubble.axisY - 2 * bubble.bubbleR) {
            bubble.status = 'bomb';
            bubble.bombBubbles.forEach((bombBubble) => {
              bombBubble.bombTime = new Date().getTime();
              bombBubble.initAxisX = bubble.axisX;
              bombBubble.initAxisY = bubble.axisY;
              bombBubble.initOpacity = bubble.bubbleOpacity;
            })
            setTimeout(function() {
              bubble.status = 'bombOver'
            }, bombTime)
          }
        })
      };

      // 绘图
      function draw() {
        canvas = document.getElementById("canvas");
        ctx = canvas.getContext("2d");
        // 清画布
        ctx.clearRect(0, 0, width, height);

        // 气泡计算和渲染
        (() => {
          cpBubblePosition();
          bubbles.forEach((item, index) => {
            if(item.status === 'rise') {
              ctx.save(); // 保存初始状态
              ctx.globalAlpha = item.bubbleOpacity;
              // 创建径向渐变
              let x1 = item.axisX;
              let y1 = item.axisY;
              let r1 = item.bubbleR;
              let x2 = item.axisX - item.bubbleR / 2;
              let y2 = item.axisY - item.bubbleR / 2;
              let r2 = item.bubbleR / 4;
              let radialGradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
              // 渐变对象着色
              radialGradient.addColorStop(0, item.bubbleColor);
              radialGradient.addColorStop(1, '#fff');
              // 使用渐变色
              ctx.fillStyle = radialGradient;
              // 清空路径
              ctx.beginPath();
              ctx.arc(item.axisX, item.axisY, item.bubbleR, 0, Math.PI * 2)
              ctx.fill();
              ctx.restore(); // 回退状态

            } else {
              item.bombBubbles.forEach((bombBubble) => {
                ctx.save(); // 保存初始状态
                ctx.translate(bombBubble.initAxisX, bombBubble.initAxisY);
                ctx.rotate(bombBubble.angle * Math.PI / 180);
                ctx.globalAlpha = bombBubble.opacity;
                // 创建渐变
                let linearGradient = ctx.createLinearGradient(bombBubble.length, 0, 2 * bombBubble.length, 0);
                // 添加颜色，颜色可以在起点和终点间添加多个
                linearGradient.addColorStop(0, '#fff');
                linearGradient.addColorStop(1, bombBubble.color);
                ctx.strokeStyle = linearGradient;
                ctx.lineWidth = 2.5;
                ctx.lineCap = 'round';
                // 清空路径
                ctx.beginPath();
                ctx.moveTo(bombBubble.length, 0);
                ctx.lineTo(bombBubble.length * 2, 0);
                ctx.stroke();
                ctx.restore(); // 回退状态
              })
            }
          })

        })();

        window.requestAnimationFrame(draw);

      }

      // 给canvas添加点击事件
      document.getElementById('canvas').addEventListener('click', bomb)

      // 每隔1300ms，500ms产生气泡
      setInterval(() => {
        createBubble();
        setTimeout(() => {
          createBubble()
        }, 500)
      }, 1300)

      // 关键帧动画
      window.requestAnimationFrame(draw);

    })();
  </script>

</html>